package com.bob.clan.wxconnector.websocket;


import com.alibaba.fastjson.JSON;
import com.bob.clan.wxconnector.component.ApplicationContextProvider;
import com.bob.clan.wxconnector.component.MpComponent;
import com.bob.clan.wxconnector.component.WebAuthorComponent;
import com.bob.clan.wxconnector.config.WebSocketConfig;
import com.bob.clan.wxconnector.config.WebSocketMsgConfig;
import com.bob.clan.wxconnector.entity.Mp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 扫码登录 企业号鉴权中心
 * Created by qx.zhangbj02320 on 2017/11/10.
 * sysCode：在mp里为各服务号定义的代码
 * clientType：1为web-login，扫码登录页；2为h5-confirm，手机端确认登录页
 * <p>
 * <p>
 * 由于@ServerEndpoint注解不是由spring容器管理, 不能注入spring容器管理的类，@Autowired无效
 * 虽然@Component是由spring容器管理且单例，但此时为多例了，所以属性需要要构造方法里手动注入
 */
@Slf4j
@ServerEndpoint("/qrLoginWebsocket/{sysCode}/{clientType}")
@ConditionalOnClass(value = WebSocketConfig.class)
@EnableScheduling
@Component //由于@ServerEndpoint是Java EE的注解，Spring Boot不会自动扫描它，因此我们需要手动注册这个Endpoint。
public class QrLoginWebsocket {
    //    @Autowired
    private WebAuthorComponent webAuthorComponent;
    //    @Autowired
    private MpComponent mpComponent;
    //    @Autowired
    private WebSocketMsgConfig webSocketMsgConfig;

    private static final Map<String, QrLoginWebsocket> callers = new HashMap<String, QrLoginWebsocket>();
    private Session session;

    public QrLoginWebsocket() {
        this.webAuthorComponent = ApplicationContextProvider.getApplicationContext().getBean(WebAuthorComponent.class);
        this.mpComponent = ApplicationContextProvider.getApplicationContext().getBean(MpComponent.class);
        this.webSocketMsgConfig = ApplicationContextProvider.getApplicationContext().getBean(WebSocketMsgConfig.class);
        log.info("===启动websocket成功===");
    }


    /**
     * 有客户端连接websocket时触发
     * clientType 1:websocketServer; 2:web-qrLogin; 3:H5-loginConfirm
     */
    @OnOpen
    public void onOpen(@PathParam("sysCode") String sysCode, @PathParam("clientType") Integer clientType, Session session) throws IOException, InterruptedException {
        if (clientType == 2) {
            this.session = session;
            callers.put(session.getId(), this);
            //建立websocket连接后，向客户端发送处理后的二维码字符串
            //建立链接时带参数,标识为登录二维码页面
            Mp mp = mpComponent.getBySysCode(sysCode);
            String qrCodeLinkUrl = webAuthorComponent.getUserBaseUrl(mp, session.getId());
            WebSocketMsgConfig.WsMsg wsMsg = webSocketMsgConfig.getByCode(121);
            wsMsg.setMsgData(qrCodeLinkUrl);
//            session.getBasicRemote().sendText("QR_CODE_LINK_URL" + qrCodeLinkUrl);
            session.getBasicRemote().sendText(JSON.toJSONString(wsMsg));
            log.info("建立链接时带参数,标识为登录二维码页面----------qrCodeLinkUrl-----" + qrCodeLinkUrl + "----sessionId-----" + session.getId());
        } else if (clientType == 3) {
            log.info("手机登录页也来了：" + sysCode);
        }
    }

    /**
     * 有客户端发送消息时触发
     */
    @OnMessage
    public void onMessage(String message, Session session) throws IOException, InterruptedException {
        log.info("on message:" + message);
        WebSocketMsgConfig.WsMsg wsMsg = webSocketMsgConfig.json2Msg(message);
        if (wsMsg.getCode() == 311) {
            log.info("手机端已扫码连接上，将通知web端隐藏二维码。接收到的消息：" + message);
            String wsSessionId = wsMsg.getWsSessionId();

            WebSocketMsgConfig.WsMsg sendMsg = webSocketMsgConfig.getByCode(123);
            sendMsg.setWsSessionId(wsSessionId);
            sendMsg.setMsgData(wsMsg.getMsgData());//转发当前扫码人的微信至web端

            //向扫码登录页面发送正在确认登录的消息
            breadCast(callers.get(wsSessionId), webSocketMsgConfig.msg2Json(sendMsg));
            log.info("向扫码登录页面当前扫码用户："+ webSocketMsgConfig.msg2Json(sendMsg));
        }else if (wsMsg.getCode() == 313) {
            log.info("已进入确认登录页面，通知登录页面，隐藏二维码，并展示头像。接收到的消息：" + message);
            String wsSessionId = wsMsg.getWsSessionId();

            WebSocketMsgConfig.WsMsg sendMsg = webSocketMsgConfig.getByCode(124);
            sendMsg.setWsSessionId(wsSessionId);
            sendMsg.setMsgData(wsMsg.getMsgData());

            //向扫码登录页面发送正在确认登录的消息
            breadCast(callers.get(wsSessionId), webSocketMsgConfig.msg2Json(sendMsg));
            log.info("向扫码登录页面当前扫码用户："+ webSocketMsgConfig.msg2Json(sendMsg));
        } else if (wsMsg.getCode() == 314) {
            log.info("客户在手机端确认登录：" + message);
            String wsSessionId = wsMsg.getWsSessionId();

            //向扫码登录页面发送允许登录的消息
            WebSocketMsgConfig.WsMsg sendMsg = webSocketMsgConfig.getByCode(124);
            sendMsg.setWsSessionId(wsSessionId);

            breadCast(callers.get(wsSessionId), webSocketMsgConfig.msg2Json(sendMsg));
            log.info("向扫码登录页面发送允许登录的消息:" + webSocketMsgConfig.msg2Json(sendMsg));
        } else if (wsMsg.getCode() == 315) {
            log.info("客户在手机端取消登录：" + message);
            String wsSessionId = wsMsg.getWsSessionId();

            //向扫码登录页面发送取消登录的消息，并重置登录二维码（客户端刷新？）
            WebSocketMsgConfig.WsMsg sendMsg = webSocketMsgConfig.getByCode(125);
            sendMsg.setWsSessionId(wsSessionId);

            breadCast(callers.get(wsSessionId), webSocketMsgConfig.msg2Json(sendMsg));
            log.info("向扫码登录页面发送取消登录的消息:" + webSocketMsgConfig.msg2Json(sendMsg));
        } else if (message.equals("*")) {
            //心跳
            log.info("通讯心跳：" + message);
            breadCast(callers.get(session.getId()), "*");
        }
    }

    /**
     * 客户端关闭时触发
     */
    @OnClose
    public void onClose(Session session, CloseReason closeReason) throws IOException, InterruptedException {
        //清除callers
//        callers.remove(this.session.getId());
        callers.remove(session.getId());
        log.info("-------------客户端关闭时触发,sessionId-------" + session.getId());
        log.info("关闭原因："+closeReason.getReasonPhrase());
        log.info("关闭码:"+closeReason.getCloseCode());
    }

    /**
     * 当网络发生错误时触发该事件
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.info("-------------当网络发生错误时触发该事件,sessionId-------" + this.session.getId());
        error.printStackTrace();
    }

    //群发信息
    protected static void breadCast(String msg) throws IOException {
        for (Map.Entry<String, QrLoginWebsocket> entry : callers.entrySet()) {
            breadCast(entry.getValue(), msg);
        }
    }

    //消息发送底层实现
    private static void breadCast(QrLoginWebsocket caller, String msg) throws IOException {
        log.info("消息发送底层实现");
        if (caller != null) {
            log.info("caller != null");
            synchronized (caller) {
                log.info("synchronized");
                caller.session.getBasicRemote().sendText(msg);
            }
        }
    }
//
//    @Scheduled(fixedRate = 2000)
//    public void sendMsg() throws IOException {
//        breadCast("---^---心跳----^----");
//    }
}

